package com.lin.manager.secure.config;

import cn.hutool.core.collection.CollectionUtil;
import com.lin.manager.log.service.LogService;
import com.lin.manager.secure.config.properties.SecurityProperties;
import com.lin.manager.secure.filter.CodeFilter;
import com.lin.manager.secure.filter.TokenFilter;
import com.lin.manager.secure.handler.CustomSecurityExceptionHandler;
import com.lin.manager.secure.handler.SecurityHandler;
import com.lin.manager.secure.service.code.CodeService;
import com.lin.manager.secure.token.AccessTokenService;
import com.lin.manager.secure.token.DefaultAccessTokenService;
import com.lin.manager.secure.token.JwtTokenGenerator;
import com.lin.manager.secure.token.TokenGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * @作者: 林江
 * @创建时间: 2024/3/15
 * @功能:
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Resource
    private SecurityProperties properties;
    @Autowired(required = false)
    @Qualifier("loginLogService")
    private LogService logService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 基础配置
        http.csrf().disable().authorizeRequests()
                // 放行请求
                .antMatchers(getPermitAll()).permitAll()
                .anyRequest().authenticated()
                .and()
                // 基于 token 机制，所以不需要 Session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
        ;

        // 配置登录成功之后的操作
        http.formLogin().loginPage("/login").permitAll()
                // .loginProcessingUrl("/login") // loginPage（“/login"）二选一
                .successHandler(securityAuthenticationHandler())
                .failureHandler(securityAuthenticationHandler())
                .and()
                .logout()
                .logoutSuccessHandler(securityAuthenticationHandler());

        // 异常配置
        CustomSecurityExceptionHandler exceptionHandler = new CustomSecurityExceptionHandler();
        http.exceptionHandling().accessDeniedHandler(exceptionHandler) // 权限异常
                .authenticationEntryPoint(exceptionHandler); // 认证异常

        // token 过滤器
        TokenFilter tokenFilter = new TokenFilter();
        tokenFilter.setIgnoreUrls(getPermitAll());
        tokenFilter.setTokenService(accessTokenService());
        http.addFilterAfter(tokenFilter, UsernamePasswordAuthenticationFilter.class);
        // 验证码过滤器
        CodeFilter codeFilter = new CodeFilter();
        codeFilter.setCodeService(codeService());
        codeFilter.setFailureHandler(securityAuthenticationHandler());
        http.addFilterBefore(codeFilter, TokenFilter.class);

        // 基于角色的的动态访问后置处理器配置
//        http.authorizeRequests().withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
//            @Override
//            public <O extends FilterSecurityInterceptor> O postProcess(O object) {
//                // 将  AccessDecisionManager ，FilterInvocationSecurityMetadataSource 放入Filter中
//                object.setAccessDecisionManager(); // 自定义决策器
//                object.setSecurityMetadataSource(); // 自定义权限的获取
//                return object;
//            }
//        });
    }

    private String[] getPermitAll() {
        List<String> list = CollectionUtil.list(false, "/doc.html", "/css/**", "/favicon.ico", "/v2/api-docs/**", "/swagger-resources/**", "/webjars/**", "/font/**", "/js/**", "/login");
        if (Objects.nonNull(properties.getIgnores())) {
            Arrays.stream(properties.getIgnores()).filter(url -> !list.contains(url)).forEach(url -> list.add(url));
        }
        return list.toArray(new String[0]);
    }

    /**
     * web静态资源放行
     *
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().mvcMatchers("/static/**");
    }

    /**
     * 配置解密器
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
//        PasswordEncoderFactories.createDelegatingPasswordEncoder();
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Order(Integer.MAX_VALUE + 13)
    @ConditionalOnMissingBean
    public TokenGenerator tokenGenerator() {
        return new JwtTokenGenerator();
    }


    @Bean
    @Order(Integer.MAX_VALUE + 12)
    @ConditionalOnMissingBean
    public SecurityHandler securityAuthenticationHandler() {
        SecurityHandler securityHandler = new SecurityHandler();
        securityHandler.setTokenService(accessTokenService());
        if (Objects.nonNull(logService)) {
            securityHandler.setLogService(logService);
        }
        return securityHandler;
    }

    @Bean
    @Order(Integer.MAX_VALUE + 11)
    @ConditionalOnMissingBean
    public AccessTokenService accessTokenService() {
        return new DefaultAccessTokenService();
    }


    @Bean
    @Order(Integer.MAX_VALUE + 10)
    @ConditionalOnMissingBean
    public CodeService codeService() {
        return code -> {
            if (StringUtils.hasText(code)) {
                return code.equalsIgnoreCase("123456");
            }
            return false;
        };
    }

}
